#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <cstring>
#include <cstdarg>
#include <sys/resource.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ptrace.h>
#include <dirent.h>
#include <sys/user.h>
#include <fstream>
#include <errno.h>
#include "okcalls.h"
#include <mysql.h>
#include <my_global.h>

using namespace std;
#ifdef __i386
#define REG_SYSCALL orig_eax
#define REG_RET eax
#define REG_ARG0 ebx
#define REG_ARG1 ecx
#else
#define REG_SYSCALL orig_rax
#define REG_RET rax
#define REG_ARG0 rdi
#define REG_ARG1 rsi

#endif

#define OJ_WT0 0
#define OJ_WT1 1
#define OJ_CI 2
#define OJ_RI 3
#define OJ_AC 4
#define OJ_PE 5
#define OJ_WA 6
#define OJ_TL 7
#define OJ_ML 8
#define OJ_OL 9
#define OJ_RE 10
#define OJ_CE 11
#define OJ_CO 12
#define OJ_TR 13

#define BUFFER_SIZE 100
#define STD_MB 1048576
#define STD_F_LIM (STD_MB<<5)
#define STD_M_LIM (STD_MB<<7)
int record_call = 0;
#define HOJ_MAX_LIMIT  -1
const int myId = 1000;
const int call_array_size = 512;
int oi_mode = 0;
static int use_ptrace = 1;
static int use_max_time = 0;
int call_counter[call_array_size] = { 0 };

static MYSQL *conn;
char *sqladdress;
char *username;
char *passwd;
char *databasename;
int sqlport;
int serial;     /* Judging/serial/ */
unsigned int ID;    /* ID used to specific the code file and sql */

bool init_mysql()
{
    const unsigned int timeout = 5; /* set connect timeout 5 second */
    if (conn == NULL)
    {
        conn = mysql_init(NULL);
        if (conn == NULL)
        {
            printf("Error %u: %s\n", mysql_errno(conn), mysql_error(conn));
            return false;
        }
        mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
        if (mysql_real_connect(conn, sqladdress, username, passwd, databasename, sqlport, NULL, 0) == 0)
        {
            printf("Error %u: %s\n", mysql_errno(conn), mysql_error(conn));
            return false;
        }
        return true;
    }
    return false;
}

bool update_mysql(int result, unsigned int ID)
{
    char resultsql[100];
    if (result == (int)OJ_AC)
        sprintf(resultsql, "update jobs set RESULT = '2' where ID = %lu", ID);
    if (result == (int)OJ_PE)
        sprintf(resultsql, "update jobs set RESULT = '3' where ID = %lu", ID);
    if (result == (int)OJ_WA)
        sprintf(resultsql, "update jobs set RESULT = '4' where ID = %lu", ID);
    if (result == (int)OJ_TL)
        sprintf(resultsql, "update jobs set RESULT = '5' where ID = %lu", ID);
    if (result == (int)OJ_ML)
        sprintf(resultsql, "update jobs set RESULT = '6' where ID = %lu", ID);
    if (result == (int)OJ_RE)
        sprintf(resultsql, "update jobs set RESULT = '7' where ID = %lu", ID);
    if (result == (int)OJ_OL)
        sprintf(resultsql, "update jobs set RESULT = '8' where ID = %lu", ID);
    if (result == (int)OJ_CE)
        sprintf(resultsql, "update jobs set RESULT = '9' where ID = %lu", ID);

    if (mysql_query(conn, resultsql) != 0)
        return false;

    return true;

}
int execute_cmd(const char * fmt, ...) {
	char cmd[BUFFER_SIZE];

	int ret = 0;
	va_list ap;

	va_start(ap, fmt);
	vsprintf(cmd, fmt, ap);
	ret = system(cmd);
	va_end(ap);
	return ret;
}
void init_syscalls_limits(int lang) {

	int i;
	memset(call_counter, 0, sizeof(call_counter));
	if (record_call) { // C & C++   //By ZK 默认record_call为0
		for (i = 0; i < call_array_size; i++) {
			call_counter[i] = 0;
		}
	} else if (lang <= 1||lang==13||lang==14) { // C & C++
		for (i = 0; i==0||LANG_CV[i]; i++) {
			call_counter[LANG_CV[i]] = HOJ_MAX_LIMIT;   //By ZK HOJ_MAX_LIMIT = -1
		}
	}

}
void prepare_files(char * filename, int namelen, char * infile, int  p_id,
		char * work_dir, char * outfile, char * userfile, int solution_id, char *oj_home) {
	char fname[BUFFER_SIZE];
	strncpy(fname, filename, namelen);
	fname[namelen] = 0;
	sprintf(infile, "%sdata/%d/%s.in", oj_home, p_id, fname);
	execute_cmd("/bin/cp '%s' %s/%d.in", infile, work_dir, solution_id);
	sprintf(outfile, "%s/data/%d/%s.out", oj_home, p_id, fname);
	sprintf(userfile, "%s/%d.out", work_dir, solution_id);
}
void delnextline(char s[]) {
	int L;
	L = strlen(s);
	while (L > 0 && (s[L - 1] == '\n' || s[L - 1] == '\r'))
		s[--L] = 0;
}
int compare(const char *file1, const char *file2) {
	//the original compare from the first version of hustoj has file size limit
	//and waste memory
	FILE *f1,*f2;
	char *s1,*s2,*p1,*p2;
	int PEflg;
	s1=new char[STD_F_LIM+512];
	s2=new char[STD_F_LIM+512];
	if (!(f1=fopen(file1,"re")))
	return OJ_AC;
	for (p1=s1;EOF!=fscanf(f1,"%s",p1);)
	while (*p1) p1++;
	fclose(f1);
	if (!(f2=fopen(file2,"re")))
	return OJ_RE;
	for (p2=s2;EOF!=fscanf(f2,"%s",p2);)
	while (*p2) p2++;
	fclose(f2);
	if (strcmp(s1,s2)!=0) {
		delete[] s1;
		delete[] s2;

		return OJ_WA;
	} else {
		f1=fopen(file1,"re");
		f2=fopen(file2,"re");
		PEflg=0;
		while (PEflg==0 && fgets(s1,STD_F_LIM,f1) && fgets(s2,STD_F_LIM,f2)) {
			delnextline(s1);
			delnextline(s2);
			if (strcmp(s1,s2)==0) continue;
			else PEflg=1;
		}
		delete [] s1;
		delete [] s2;
		fclose(f1);fclose(f2);
		if (PEflg) return OJ_PE;
		else return OJ_AC;
	}
}
long get_file_size(const char * filename) {
	struct stat f_stat;

	if (stat(filename, &f_stat) == -1) {
		return 0;
	}

	return (long) f_stat.st_size;
}
void print_runtimeerror(char * err, char *work_dir, int solution_id) {
	char errorfilename[100];
	sprintf(errorfilename, "%s/error%d", work_dir, solution_id);
	FILE *ferr = fopen(errorfilename, "a+");
	fprintf(ferr, "Runtime Error:%s\n", err);
	fclose(ferr);
}
int get_proc_status(int pid, const char * mark) {
	FILE * pf;
	char fn[BUFFER_SIZE], buf[BUFFER_SIZE];
	int ret = 0;
	sprintf(fn, "/proc/%d/status", pid);
	pf = fopen(fn, "re");
	int m = strlen(mark);
	while (pf && fgets(buf, BUFFER_SIZE - 1, pf)) {

		buf[strlen(buf) - 1] = 0;
		if (strncmp(buf, mark, m) == 0) {
			sscanf(buf + m + 1, "%d", &ret);
		}
	}
	if (pf)
		fclose(pf);
	return ret;
}
void clean_workdir(char * work_dir) {
	execute_cmd("/bin/umount %s/proc", work_dir);
	execute_cmd("/bin/umount %s/dev", work_dir);
	execute_cmd("/bin/umount %s/lib", work_dir);
	execute_cmd("/bin/umount %s/lib64", work_dir);
	execute_cmd("/bin/umount %s/etc/alternatives", work_dir);
	execute_cmd("/bin/umount %s/usr", work_dir);
	execute_cmd("/bin/umount %s/bin", work_dir);
	execute_cmd("/bin/umount %s/proc", work_dir);
	execute_cmd("/bin/umount bin usr lib lib64 etc/alternatives proc");
	execute_cmd("/bin/umount *");
}

void run_solution(int & lang, char * work_dir, int & time_lmt, int & usedtime,
		int & mem_lmt, int solution_id) {
	chdir(work_dir);
	// open the files
	char in[100];
	sprintf(in, "%d.in", solution_id);
	char out[100];
	sprintf(out, "%d.out", solution_id);
	char error[100];
	sprintf(error, "error%d", solution_id);
	freopen(in, "r", stdin);
	freopen(out, "w", stdout);
	freopen(error, "a+", stderr);
	// trace me
	ptrace(PTRACE_TRACEME, 0, NULL, NULL);
	// run me
	chroot(work_dir);

	while (setgid(myId) != 0)
		sleep(1);
	while (setuid(myId) != 0)
		sleep(1);
	while (setresuid(myId, myId, myId) != 0)
		sleep(1);

	// child
	// set the limit
	struct rlimit LIM; // time limit, file limit& memory limit
	// time limit
	LIM.rlim_cur = (time_lmt - usedtime / 1000) + 1;
	LIM.rlim_max = LIM.rlim_cur;
	//if(DEBUG) printf("LIM_CPU=%d",(int)(LIM.rlim_cur));
	setrlimit(RLIMIT_CPU, &LIM);
	alarm(0);
	alarm(time_lmt * 10);

	// file limit
	LIM.rlim_max = STD_F_LIM + STD_MB;
	LIM.rlim_cur = STD_F_LIM;
	setrlimit(RLIMIT_FSIZE, &LIM);
	// proc limit
	LIM.rlim_cur = LIM.rlim_max = 1;

	setrlimit(RLIMIT_NPROC, &LIM);

	// set the stack
	LIM.rlim_cur = STD_MB << 6;
	LIM.rlim_max = STD_MB << 6;
	setrlimit(RLIMIT_STACK, &LIM);
	// set the memory
	LIM.rlim_cur = STD_MB * mem_lmt / 2 * 3;
	LIM.rlim_max = STD_MB * mem_lmt * 2;
	setrlimit(RLIMIT_AS, &LIM);

	char runName[100];
	sprintf(runName, "./%d", solution_id);
	execl(runName, runName, (char *) NULL);

	fflush(stderr);
	exit(0);

}
void watch_solution(pid_t pidApp, char * infile, int & ACflg,
		char * userfile, char * outfile, int solution_id,
		int & topmemory, int mem_lmt, int & usedtime, int time_lmt, int & p_id,
		int & PEflg, char * work_dir) {
	// parent
	int tempmemory;

	int status, sig, exitcode;
	struct user_regs_struct reg;
	struct rusage ruse;
	if(topmemory==0)
			topmemory= get_proc_status(pidApp, "VmRSS:") << 10;
	while (1) {
		// check the usage

		wait4(pidApp, &status, 0, &ruse);


		tempmemory = get_proc_status(pidApp, "VmPeak:") << 10;

		if (tempmemory > topmemory)
			topmemory = tempmemory;
		if (topmemory > mem_lmt * STD_MB) {
			if (ACflg == OJ_AC)
				ACflg = OJ_ML;
			ptrace(PTRACE_KILL, pidApp, NULL, NULL);
			break;
		}

		if (WIFEXITED(status))
			break;

		if(	get_file_size(userfile)
						> get_file_size(outfile) * 2 + 1024) {
			ACflg = OJ_OL;
			ptrace(PTRACE_KILL, pidApp, NULL, NULL);
			break;
		}

		exitcode = WEXITSTATUS(status);
			if (exitcode == 0x05 || exitcode == 0)
			{}
			else {
			if (ACflg == OJ_AC) {
				switch (exitcode) {
				case SIGCHLD:
				case SIGALRM:
					alarm(0);
				case SIGKILL:
				case SIGXCPU:
					ACflg = OJ_TL;
					break;
				case SIGXFSZ:
					ACflg = OJ_OL;
					break;
				default:
					ACflg = OJ_RE;
				}
				print_runtimeerror(strsignal(exitcode), work_dir, solution_id);
			}
			ptrace(PTRACE_KILL, pidApp, NULL, NULL);
			break;
		}

		if (WIFSIGNALED(status)) {
			/*  WIFSIGNALED: if the process is terminated by signal
			 *
			 *  psignal(int sig, char *s)，like perror(char *s)，print out s, with error msg from system of sig
			 * sig = 5 means Trace/breakpoint trap
			 * sig = 11 means Segmentation fault
			 * sig = 25 means File size limit exceeded
			 */
			sig = WTERMSIG(status);

			
			if (ACflg == OJ_AC) {
				switch (sig) {
				case SIGCHLD:
				case SIGALRM:
					alarm(0);
				case SIGKILL:
				case SIGXCPU:
					ACflg = OJ_TL;
					break;
				case SIGXFSZ:
					ACflg = OJ_OL;
					break;

				default:
					ACflg = OJ_RE;
				}
				print_runtimeerror(strsignal(sig), work_dir, solution_id);
			}
			break;
		}
		/*     comment from http://www.felix021.com/blog/read.php?1662

		 WIFSTOPPED: return true if the process is paused or stopped while ptrace is watching on it
		 WSTOPSIG: get the signal if it was stopped by signal
		 */

		// check the system calls
		ptrace(PTRACE_GETREGS, pidApp, NULL, &reg);
		if (call_counter[reg.REG_SYSCALL] ){
			//call_counter[reg.REG_SYSCALL]--;
		}else if (record_call) {
			call_counter[reg.REG_SYSCALL] = 1;

		}else { 
			ACflg = OJ_RE;
			char error[BUFFER_SIZE];
			ptrace(PTRACE_KILL, pidApp, NULL, NULL);
			char outputREfn[20];
			sprintf(outputREfn, "%d.output", solution_id);
			cout << outputREfn << endl;
			ofstream outputRE(outputREfn);
			outputRE << (int)OJ_RE << endl;
			execute_cmd("rm %d.in %d.out error%d %d", solution_id, solution_id, solution_id, solution_id);
		}


		ptrace(PTRACE_SYSCALL, pidApp, NULL, NULL);
	}
	usedtime += (ruse.ru_utime.tv_sec * 1000 + ruse.ru_utime.tv_usec / 1000);
	usedtime += (ruse.ru_stime.tv_sec * 1000 + ruse.ru_stime.tv_usec / 1000);

}
int isInFile(const char fname[]) {
	int l = strlen(fname);
	if (l <= 3 || strcmp(fname + l - 3, ".in") != 0)
		return 0;
	else
		return l - 3;
}
void judge_solution(int & ACflg, int & usedtime, int time_lmt,
		int p_id, char * infile, char * outfile, char * userfile, int & PEflg,
	 char * work_dir, int & topmemory, int mem_lmt,
		int solution_id, double num_of_test) {
	int comp_res;
	if (!oi_mode)
		num_of_test = 1.0;
	if (ACflg == OJ_AC
			&& usedtime > time_lmt * 1000 * (use_max_time ? 1 : num_of_test))
		ACflg = OJ_TL; //By ZK time limit
	if (topmemory > mem_lmt * STD_MB)
		ACflg = OJ_ML; //issues79  //By ZK memory limit
	// compare
	if (ACflg == OJ_AC) {

			comp_res = compare(outfile, userfile);

		if (comp_res == OJ_WA) {
			ACflg = OJ_WA;
		} else if (comp_res == OJ_PE)
			PEflg = OJ_PE;
		ACflg = comp_res;
	}

}


int compile(int solution_id, char * work_dir) {
	pid_t pid;


	char filename[20];
    sprintf(filename, "%d.cpp", solution_id);
	char outputfilename[20];
	sprintf(outputfilename, "%d", solution_id);
	char * CP_X[] = { "g++", filename, "-o", outputfilename, "-fno-asm", "-Wall",
			"-lm", "--static", "-std=c++0x", "-DONLINE_JUDGE", NULL };


	pid = fork();
	cout << "pid: " << pid << endl;
	if (pid == 0) {  
		cout << "solution: " << solution_id << endl;
		struct rlimit LIM;
		LIM.rlim_max = 60;
		LIM.rlim_cur = 60;
		setrlimit(RLIMIT_CPU, &LIM);
		alarm(60);
		LIM.rlim_max = 10 * STD_MB;
		LIM.rlim_cur = 10 * STD_MB;
		setrlimit(RLIMIT_FSIZE, &LIM);

		LIM.rlim_max = STD_MB *256 ;
		LIM.rlim_cur = STD_MB *256 ;
		setrlimit(RLIMIT_AS, &LIM);
		char errorfilename[20];
		sprintf(errorfilename, "%d.error", solution_id);
		freopen(errorfilename, "w", stderr);
		char nowdir[100];
		getcwd(nowdir, 100);
        cout << "before chroot: " << nowdir << endl;
        chroot(work_dir);

		while(setgid(myId)!=0) sleep(1);
        while(setuid(myId)!=0) sleep(1);
        while(setresuid(myId, myId, myId)!=0) sleep(1);

		getcwd(nowdir, 100);
		cout <<"nowdir after chroot: " << nowdir << endl;
		execvp(CP_X[0], (char * const *) CP_X);
        printf("%s\n", strerror(errno));
		exit(0);
	} else { 
		int status = 0;

		waitpid(pid, &status, 0);

		return status;
	}

}
int main(int argc, char** argv) {


    sqladdress = argv[6];
    databasename = argv[7];
    username = argv[8];
    passwd = argv[9];

    serial = atoi(argv[4]);

    init_mysql();
	char work_dir[BUFFER_SIZE];
	char user_id[BUFFER_SIZE];
	int p_id, time_lmt, mem_lmt, lang, isspj, sim, sim_s_id, max_case_time = 0;

	int solution_id = atoi(argv[2]);
	p_id = atoi(argv[1]);
	char oj_home[20];
	strcpy(oj_home, argv[3]);
    sprintf(work_dir, "%sJudging/%s", oj_home, argv[4]);
	chdir(work_dir);

	lang = 1;   //直接以C++模式

	//获取time_lmt,mem_lmt现在直接设为定值
	time_lmt = 1;   //设置时间限制为1s
	mem_lmt = 128;

	if (time_lmt > 300 || time_lmt < 1)
		time_lmt = 300;
	if (mem_lmt > 1024 || mem_lmt < 1)
		mem_lmt = 1024;

	// compile
	// set the result to compiling
	int Compile_OK;

	Compile_OK = compile(solution_id, work_dir);
	if (Compile_OK != 0) {
        /* 改为数据库提交 */
        update_mysql(OJ_CE, (unsigned int)solution_id);
		exit(0);
	}
	else { //编译没有问题，删除错误输出文件
		execute_cmd("rm %s/%d.error", work_dir, solution_id);
	}
	//运行测试样例
	char fullpath[BUFFER_SIZE];
	char infile[BUFFER_SIZE];
	char outfile[BUFFER_SIZE];
	char userfile[BUFFER_SIZE];
	sprintf(fullpath, "%sdata/%d", oj_home, p_id); // the fullpath of data dir

	// open DIRs
	DIR *dp;
	dirent *dirp;

	dp = opendir(fullpath);
	int ACflg, PEflg;
	ACflg = PEflg = OJ_AC;
	int namelen;
	int usedtime = 0, topmemory = 0;

	// read files and run
	double pass_rate = 0.0;
	int num_of_test = 0;
	int finalACflg = ACflg;

	for (; (ACflg == OJ_AC|| ACflg == OJ_PE) && (dirp = readdir(dp)) != NULL;) {

		namelen = isInFile(dirp->d_name); // check if the file is *.in or not
		if (namelen == 0)
			continue;

		prepare_files(dirp->d_name, namelen, infile, p_id, work_dir, outfile,
				userfile, solution_id, oj_home);
		init_syscalls_limits(1);

		pid_t pidApp = fork();

		if (pidApp == 0) {

			run_solution(lang, work_dir, time_lmt, usedtime, mem_lmt, solution_id);
		} else {

			num_of_test++;

			watch_solution(pidApp, infile, ACflg, userfile, outfile,
					solution_id, topmemory, mem_lmt, usedtime, time_lmt,
					p_id, PEflg, work_dir);

			judge_solution(ACflg, usedtime, time_lmt,  p_id, infile,
					outfile, userfile, PEflg,  work_dir, topmemory,
					mem_lmt, solution_id, num_of_test);
			if (use_max_time) {
				max_case_time =
						usedtime > max_case_time ? usedtime : max_case_time;
				usedtime = 0;
			}
			//clean_session(pidApp);
		}
		if (oi_mode) {
			if (ACflg == OJ_AC) {
				++pass_rate;
			}
			if (finalACflg < ACflg) {
				finalACflg = ACflg;
			}

			ACflg = OJ_AC;
		}
	}
	if (ACflg == OJ_AC && PEflg == OJ_PE)
		ACflg = OJ_PE;

	sim = 0;  //不使用sim比较代码相似度


	char resultFile[100];
	sprintf(resultFile, "%s/%d.output", work_dir, solution_id);
	if ((oi_mode && finalACflg == OJ_RE) || ACflg == OJ_RE) {

/*		ofstream output(resultFile);
		output << (int)OJ_RE << endl;
		output.close();
        */
        /* 改为数据库提交 */
        update_mysql((int)OJ_RE, (unsigned int)solution_id);
		execute_cmd("rm %d.in %d.out error%d %d", solution_id, solution_id, solution_id, solution_id);
		exit(-1);
	}
	if (use_max_time) {
		usedtime = max_case_time;
	}
	if (ACflg == OJ_TL) {
		usedtime = time_lmt * 1000;
	}

    /*
	ofstream output(resultFile);
	output << (int)ACflg << endl;
	output << "usedtime: " << usedtime << endl;
	output << "topmemory: " << (topmemory >> 10) << endl;
    */
    /* 改为数据库提交 */
    update_mysql((int)ACflg, (unsigned int)solution_id);
	closedir(dp);
	execute_cmd("rm %d.in %d.out error%d %d", solution_id, solution_id, solution_id, solution_id);
	return 0;
}
